package com.ruyiadmin.springcloud.producer.service.impls.system;

import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruyiadmin.springcloud.producer.common.awares.core.RuYiAdminContextAware;
import com.ruyiadmin.springcloud.producer.common.beans.system.CodeGeneratorConfig;
import com.ruyiadmin.springcloud.producer.common.beans.system.SystemCacheConfig;
import com.ruyiadmin.springcloud.producer.common.beans.system.SystemConfig;
import com.ruyiadmin.springcloud.producer.common.beans.system.SystemRedisConfig;
import com.ruyiadmin.springcloud.producer.common.components.system.RuYiLogComponent;
import com.ruyiadmin.springcloud.producer.common.constants.system.Guid;
import com.ruyiadmin.springcloud.producer.common.core.business.enums.*;
import com.ruyiadmin.springcloud.producer.common.core.system.entities.ActionResult;
import com.ruyiadmin.springcloud.producer.common.core.system.entities.QueryCondition;
import com.ruyiadmin.springcloud.producer.common.core.system.entities.QueryResult;
import com.ruyiadmin.springcloud.producer.common.core.system.entities.SystemMessage;
import com.ruyiadmin.springcloud.producer.common.core.system.enums.MessageType;
import com.ruyiadmin.springcloud.producer.common.events.system.SysLogEvent;
import com.ruyiadmin.springcloud.producer.common.exceptions.RuYiAdminCustomException;
import com.ruyiadmin.springcloud.producer.common.utils.core.RuYiAesUtil;
import com.ruyiadmin.springcloud.producer.common.components.core.RuYiRedisComponent;
import com.ruyiadmin.springcloud.producer.common.utils.core.RuYiRsaUtil;
import com.ruyiadmin.springcloud.producer.domain.dto.system.LoginDTO;
import com.ruyiadmin.springcloud.producer.domain.dto.system.SysMenuDTO;
import com.ruyiadmin.springcloud.producer.domain.dto.system.SysUserDTO;
import com.ruyiadmin.springcloud.producer.domain.entity.system.*;
import com.ruyiadmin.springcloud.producer.repository.system.ISysUserRepository;
import com.ruyiadmin.springcloud.producer.service.iservices.system.ISysLanguageService;
import com.ruyiadmin.springcloud.producer.service.iservices.system.ISysUserService;
import eu.bitwalker.useragentutils.UserAgent;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.jms.core.JmsMessagingTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.jms.Topic;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 系统用户 服务实现类
 * </p>
 *
 * @author RuYiAdmin
 * @since 2022-06-28
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class SysUserServiceImpl extends ServiceImpl<ISysUserRepository, SysUser> implements ISysUserService {

    //region 实现类私有属性

    private final ISysUserRepository userRepository;
    private final RuYiRedisComponent redisUtils;
    private final SystemCacheConfig systemCacheConfig;
    private final SystemConfig systemConfig;
    private final SystemRedisConfig systemRedisConfig;
    private final ISysLanguageService languageService;
    private final CodeGeneratorConfig codeGeneratorConfig;
    private final Topic topic;
    private final JmsMessagingTemplate jmsMessagingTemplate;
    private final RuYiLogComponent logUtil;

    //endregion

    //region 用户登录系统

    /**
     * 用户登录系统
     *
     * @param login LoginDTO
     * @return ActionResult
     * @throws Exception 异常
     */
    @Override
    public ActionResult logon(LoginDTO login) throws Exception {
        Map<String, Object> obj = new HashMap<>();
        String privateKey = systemConfig.getRsaPrivateKey();

        //region 用户信息解密

        login.setUserName(RuYiRsaUtil.decrypt(login.getUserName(), privateKey));
        login.setPassword(RuYiRsaUtil.decrypt(login.getPassword(), privateKey));
        login.setCaptchaId(RuYiRsaUtil.decrypt(login.getCaptchaId(), privateKey));
        login.setCaptcha(RuYiRsaUtil.decrypt(login.getCaptcha(), privateKey));

        //endregion

        Object resultNum = this.redisUtils.get(login.getCaptchaId());
        if (resultNum != null && resultNum.toString().equals(login.getCaptcha())) {
            //删除验证码
            this.redisUtils.del(login.getCaptchaId());

            //region 获取用户信息

            //用户名去盐
            login.setUserName(login.getUserName().replace("_" + login.getCaptchaId(), ""));
            //密码去盐
            login.setPassword(login.getPassword().replace("_" + login.getCaptchaId(), ""));

            Object value = this.redisUtils.get(systemCacheConfig.getUserCacheName());
            List<SysUserDTO> users = JSON.parseArray(value.toString(), SysUserDTO.class);

            List<SysUserDTO> tempUsers = users.stream().
                    filter(t -> t.getIsdel() == DeletionType.Undeleted.ordinal()).
                    filter(t -> t.getLogonName().equals(login.getUserName())).
                    collect(Collectors.toList());
            SysUserDTO tempUser = tempUsers.size() > 0 ? tempUsers.get(0) : null;
            if (tempUser == null) {
                throw new RuYiAdminCustomException("user is invalid");
            }

            //AesKey
            String aesKey = systemConfig.getAesKey();
            //AES加密
            login.setPassword(RuYiAesUtil.encrypt(login.getPassword() + tempUser.getSalt(), aesKey));

            List<SysUserDTO> list = users.stream().
                    filter(t -> t.getIsdel() == DeletionType.Undeleted.ordinal()).
                    filter(t -> t.getLogonName().equals(login.getUserName())).
                    filter(t -> t.getPassword().equals(login.getPassword())).
                    filter(t -> t.getIsEnabled() == UserStatus.Enabled.ordinal())
                    .collect(Collectors.toList());

            //endregion

            if (list.size() == 1) {
                //region 用户状态合法

                SysUserDTO user = list.get(0);

                String tokenKey = systemConfig.getTokenKey();
                int tokenExpiration = systemConfig.getUserTokenExpiration();
                int limitCount = systemConfig.getLogonCountLimit();

                String pattern = String.format("%s%s_*", systemRedisConfig.getPattern(), user.getId());

                if (limitCount == 1) {
                    //region 仅允许一次登录

                    List<String> keys = this.redisUtils.scan(pattern);
                    if (keys.size() > 0) {
                        for (String item : keys) {
                            //region 强制用户下线

                            //删除用户token
                            this.redisUtils.del(item);

                            SystemMessage msg = new SystemMessage();
                            msg.setMessage("ForceLogout");
                            msg.setMessageType(MessageType.ForceLogout);
                            msg.setObject(user);

                            this.jmsMessagingTemplate.convertAndSend(topic, JSON.toJSONString(msg));

                            //endregion
                        }
                    }

                    String token = String.format(tokenKey, user.getId(), UUID.randomUUID());
                    user.setToken(token);
                    user.setTokenExpiration(tokenExpiration * 60);
                    //登录授权
                    obj = this.getPermissions(user);

                    //endregion
                } else if (limitCount > 1) {
                    //region 允许多次登录

                    List<String> keys = this.redisUtils.scan(pattern);
                    int count = keys.size();
                    //达到登录上限
                    if (count == limitCount) {
                        throw new RuYiAdminCustomException("logon count limited");
                    }
                    //未达上限
                    else if (count < limitCount) {
                        String token = String.format(tokenKey, user.getId(), UUID.randomUUID());
                        user.setToken(token);
                        user.setTokenExpiration(tokenExpiration * 60);
                        //登录授权
                        obj = this.getPermissions(user);
                    }

                    //endregion
                }

                //endregion
            } else {
                throw new RuYiAdminCustomException("user is invalid");
            }
        } else {
            throw new RuYiAdminCustomException("captcha is invalid");
        }

        return ActionResult.success(JSON.toJSON(obj));
    }

    //endregion

    //region 加载系统用户缓存

    /**
     * 加载系统用户缓存
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void loadSystemUserCache() {
        List<SysUserDTO> users = this.userRepository.queryAllSysUser();
        this.redisUtils.set(systemCacheConfig.getUserCacheName(), JSON.toJSONString(users));
        log.info("RuYiAdmin sys users cache loaded");
    }

    //endregion

    //region 清理系统用户缓存

    /**
     * 清理系统用户缓存
     */
    @Override
    public void clearSystemUserCache() {
        this.redisUtils.del(systemCacheConfig.getUserCacheName());
        log.info("RuYiAdmin sys users cache cleared");
    }

    //endregion

    //region 获取在线用户

    /**
     * 获取在线用户
     *
     * @return QueryResult
     */
    @Override
    public QueryResult<SysUserDTO> queryOnlineUsers() {
        List<SysUserDTO> list = new ArrayList<>();

        String pattern = String.format("%s*", systemRedisConfig.getPattern());
        List<String> keys = this.redisUtils.scan(pattern);

        keys.forEach(t -> {
            Object value = this.redisUtils.get(t);
            SysUserDTO user = JSON.parseObject(value.toString(), SysUserDTO.class);
            list.add(user);
        });

        return QueryResult.success(list.size(), list);
    }

    //endregion

    //region 用户退出登录

    /**
     * 用户退出登录
     *
     * @param token 口令
     * @return ActionResult
     */
    @Override
    public ActionResult userLogout(String token) {
        this.redisUtils.del(token);
        return ActionResult.ok();
    }

    //endregion

    //region 强制用户退出

    /**
     * 强制用户退出
     *
     * @param token 口令
     * @return ActionResult
     * @throws Exception 异常
     */
    @Override
    public ActionResult forceUserLogout(String token) throws Exception {

        //region 强制用户下线

        Object value = this.redisUtils.get(token);
        SysUserDTO user = JSON.parseObject(value.toString(), SysUserDTO.class);

        //删除用户token
        this.redisUtils.del(token);

        SystemMessage msg = new SystemMessage();
        msg.setMessage("ForceLogout");
        msg.setMessageType(MessageType.ForceLogout);
        msg.setObject(user);

        this.jmsMessagingTemplate.convertAndSend(topic, JSON.toJSONString(msg));

        //endregion

        //region 记录下线日志

        SysLog log = logUtil.getSysLog();
        log.setOperationType(OperationType.ForceLogout.ordinal());
        log.setRemark(user.getDisplayName() + "/" + user.getLogonName() + "被超级管理员强制下线");

        // 发送异步日志事件
        RuYiAdminContextAware.publishEvent(new SysLogEvent(log));

        //endregion

        return ActionResult.ok();
    }

    //endregion

    //region 获取机构用户

    /**
     * 获取机构用户
     *
     * @param queryCondition 查询条件
     * @return QueryResult
     */
    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public QueryResult<SysUserDTO> queryOrgUserInfo(QueryCondition queryCondition) {
        queryCondition.setSort(StringUtils.EMPTY);

        QueryWrapper<SysUserDTO> wrapper = new QueryWrapper<>();
        queryCondition.getQueryWrapper(wrapper);
        Page<SysUserDTO> page = new Page<>(queryCondition.getPageIndex(), queryCondition.getPageSize());

        IPage<SysUserDTO> records = this.userRepository.queryOrgUserInfo(page, wrapper);

        return QueryResult.success(records.getTotal(), records.getRecords());
    }

    //endregion

    //region 查询所有用户信息

    /**
     * <p>
     * 查询所有用户信息
     * </p>
     *
     * @return 用户集合
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<SysUser> queryAllUsers() {
        return this.userRepository.queryAllUsers();
    }

    //endregion

    //region 服务私有方法

    //region 获取用户权限

    /**
     * 获取用户权限
     *
     * @param user 用户信息
     * @return HashMap
     * @throws Exception 异常
     */
    private Map<String, Object> getPermissions(SysUserDTO user) throws Exception {
        Map<String, Object> obj = new HashMap<>();

        List<SysMenuDTO> menuList = new ArrayList<>();
        //超级用户
        if (user.getIsSupperAdmin() == YesNo.YES.ordinal()) {
            Object value = this.redisUtils.get(systemCacheConfig.getMenuCacheName());
            menuList = JSON.parseArray(value.toString(), SysMenuDTO.class);
        }
        //普通用户
        else {

            //region 获取用户角色

            Object value = this.redisUtils.get(systemCacheConfig.getRoleAndUserCacheName());
            List<SysRoleUser> roleUsers = JSON.parseArray(value.toString(), SysRoleUser.class);

            roleUsers = roleUsers.stream().
                    filter(t -> t.getIsdel() == DeletionType.Undeleted.ordinal()).
                    filter(t -> t.getUserId().equals(user.getId())).
                    collect(Collectors.toList());

            //endregion

            //用户拥有角色
            if (roleUsers.size() > 0) {

                //获取角色菜单
                value = this.redisUtils.get(systemCacheConfig.getRoleAndMenuCacheName());
                List<SysRoleMenu> roleMenus = JSON.parseArray(value.toString(), SysRoleMenu.class);

                //获取系统菜单
                value = this.redisUtils.get(systemCacheConfig.getMenuCacheName());
                List<SysMenuDTO> menus = JSON.parseArray(value.toString(), SysMenuDTO.class);

                //region 遍历用户菜单

                for (SysRoleUser roleUser : roleUsers) {
                    //遍历用户拥有的角色
                    List<SysRoleMenu> subRoleMenus = roleMenus.stream().
                            filter(t -> t.getIsdel() == DeletionType.Undeleted.ordinal()).
                            filter(t -> t.getRoleId().equals(roleUser.getRoleId())).
                            collect(Collectors.toList());
                    //遍历用户拥有的菜单
                    for (SysRoleMenu roleMenu : subRoleMenus) {
                        List<SysMenuDTO> menuTemps = menus.stream().
                                filter(t -> t.getIsdel() == DeletionType.Undeleted.ordinal()).
                                filter(t -> t.getId().equals(roleMenu.getMenuId())).
                                collect(Collectors.toList());
                        SysMenuDTO menu = menuTemps.size() > 0 ? menuTemps.get(0) : null;
                        if (menu != null && menuList.stream().noneMatch(t -> t.getId().equals(menu.getId()))) {
                            menuList.add(menu);
                        }
                    }
                }

                //endregion
            }
        }

        //region 装填用户菜单

        List<SysMenuDTO> permissions = new ArrayList<>();

        //一级菜单
        List<SysMenuDTO> parentNodes = menuList.stream().
                filter(t -> t.getMenuType() == MenuType.Menu.ordinal()).
                filter(t -> t.getParentId() == null).
                sorted(Comparator.comparing(SysMenuDTO::getSerialNumber)).
                collect(Collectors.toList());

        for (SysMenuDTO node : parentNodes) {
            node.setChildren(new ArrayList<>());

            //二级菜单
            List<SysMenuDTO> menuSons = menuList.stream().
                    filter(t -> t.getParentId() != null && t.getParentId().equals(node.getId())).
                    filter(t -> t.getMenuType() == MenuType.Menu.ordinal()).
                    sorted(Comparator.comparing(SysMenuDTO::getSerialNumber)).
                    collect(Collectors.toList());

            for (SysMenuDTO subNode : menuSons) {
                //三级菜单：按钮、视图
                List<SysMenuDTO> subItems = menuList.stream().
                        filter(t -> t.getParentId() != null && t.getParentId().equals(subNode.getId())).
                        filter(t -> t.getMenuType() == MenuType.Button.ordinal() ||
                                t.getMenuType() == MenuType.View.ordinal()).
                        sorted(Comparator.comparing(SysMenuDTO::getSerialNumber)).
                        collect(Collectors.toList());
                if (subItems.size() > 0) {
                    subNode.setChildren(new ArrayList<>());

                    subNode.getChildren().addAll(subItems);

                    node.getChildren().add(subNode);
                }
            }

            permissions.add(node);
        }

        //endregion

        //初始化菜单多语
        this.initLanguages(permissions);

        //初始化用户口令
        this.redisUtils.set(user.getToken(), JSON.toJSONString(user), user.getTokenExpiration());

        //region 记录登录日志

        //获取请求url,ip,httpMethod
        HttpServletRequest request = ((ServletRequestAttributes) Objects
                .requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();

        UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("user-agent"));
        String clientType = userAgent.getOperatingSystem().getDeviceType().toString();//客户端类型  手机、电脑、平板
        String os = userAgent.getOperatingSystem().getName();//操作系统类型
        String browser = userAgent.getBrowser().toString();//浏览器类型

        SysLog sysLog = new SysLog();

        sysLog.setId(UUID.randomUUID().toString());
        sysLog.setUserId(user.getId());
        sysLog.setUserName(user.getDisplayName() + "/" + user.getLogonName());
        sysLog.setOrgId(StringUtils.isEmpty(user.getOrgId()) ? Guid.Empty : user.getOrgId());
        sysLog.setOrgName(StringUtils.isEmpty(user.getOrgName()) ? StringUtils.EMPTY : user.getOrgName());
        sysLog.setSystem(clientType + "，" + os);
        sysLog.setBrowser(browser);
        sysLog.setIp(ServletUtil.getClientIP(request));
        sysLog.setRequestMethod(request.getMethod());
        sysLog.setRequestUrl(URLUtil.getPath(request.getRequestURI()));
        sysLog.setParams(HttpUtil.toParams(request.getParameterMap()));
        sysLog.setResult(StringUtils.EMPTY);
        sysLog.setOldValue(StringUtils.EMPTY);
        sysLog.setNewValue(StringUtils.EMPTY);
        sysLog.setRemark(sysLog.getUserName() + "于" + LocalDateTimeUtil.now()
                + "访问了" + sysLog.getRequestUrl() + "接口");
        sysLog.setIsdel(DeletionType.Undeleted.ordinal());
        sysLog.setCreator(user.getId());
        sysLog.setCreateTime(LocalDateTimeUtil.now());
        sysLog.setModifier(user.getId());
        sysLog.setModifyTime(LocalDateTimeUtil.now());
        sysLog.setVersionId(UUID.randomUUID().toString());
        sysLog.setOperationType(OperationType.Logon.ordinal());

        // 发送异步日志事件
        RuYiAdminContextAware.publishEvent(new SysLogEvent(sysLog));

        //endregion

        //装填返回结果
        obj.put("user", user);
        obj.put("permissions", permissions);
        obj.put("codeGeneratorConfig", codeGeneratorConfig);

        return obj;
    }

    //endregion

    //region 初始化菜单多语

    /**
     * 初始化菜单多语
     */
    private void initLanguages(List<SysMenuDTO> list) {
        if (list.size() > 0) {
            List<SysLanguage> languages = this.languageService.list();

            SysLanguage lanEn = languages.stream().
                    filter(t -> t.getLanguageName().equals("en-US")).
                    collect(Collectors.toList()).
                    get(0);
            SysLanguage lanRu = languages.stream().
                    filter(t -> t.getLanguageName().equals("ru-RU")).
                    collect(Collectors.toList()).
                    get(0);

            Object value = this.redisUtils.get(systemCacheConfig.getMenuAndLanguageCacheName());
            List<SysMenuLanguage> menuLanguages = JSON.parseArray(value.toString(), SysMenuLanguage.class);

            for (SysMenuDTO item : list) {

                List<SysMenuLanguage> enMenus = menuLanguages.stream().
                        filter(t -> t.getMenuId().equals(item.getId())).
                        filter(t -> t.getLanguageId().equals(lanEn.getId())).
                        filter(t -> t.getIsdel() == DeletionType.Undeleted.ordinal()).
                        collect(Collectors.toList());
                SysMenuLanguage enMenu = enMenus.size() > 0 ? enMenus.get(0) : null;

                List<SysMenuLanguage> ruMenus = menuLanguages.stream().
                        filter(t -> t.getMenuId().equals(item.getId())).
                        filter(t -> t.getLanguageId().equals(lanRu.getId())).
                        filter(t -> t.getIsdel() == DeletionType.Undeleted.ordinal()).
                        collect(Collectors.toList());
                SysMenuLanguage ruMenu = ruMenus.size() > 0 ? ruMenus.get(0) : null;

                if (enMenu != null) {
                    item.setMenuNameEn(enMenu.getMenuName());
                }
                if (ruMenu != null) {
                    item.setMenuNameRu(ruMenu.getMenuName());
                }

                if (item.getChildren() != null && item.getChildren().size() > 0) {
                    this.initLanguages(item.getChildren());
                }
            }
        }
    }

    //endregion

    //endregion

}
